using System;
using System.Collections.Generic;
using System.Text;
using Dogfight2008.Common;
using OpenTK.Math;
using System.Drawing;
using OpenTK.OpenGL;
using System.Media;

namespace Dogfight2008
{
  interface ICollideable
  {
    Box2d GetBox2d();
  }

  interface IActive
  {
    void Tick(double deltaMS);
  }

  interface IRenderable
  {
    void Render(IRenderer renderer);
  }

  public class Game : IGame
  {
    int playerOnePlanesLeft;
    int playerTwoPlanesLeft;
    Plane[] planes = new Plane[2];

    GameDB db = null;
    List<CollisionRule> rules = null;

    public bool GameOver
    {
      get
      {
        return playerOnePlanesLeft == 0 || playerTwoPlanesLeft == 0;
      }
    }

    public void ResetGame()
    {
      rules = new List<CollisionRule>();
      rules.Add(ShotCollisionBoxRule());
      rules.Add(PlanePlaneRule());
      rules.Add(PlaneSideBoxRule());
      rules.Add(PlaneTopBoxRule());
      rules.Add(PlaneBottomBoxRule());
      rules.Add(PlaneShotRule());

      db = new GameDB(typeof(ICollideable), typeof(IActive), typeof(IRenderable));
      db.Add(new Backdrop());
      ResetPlanes();
      playerTwoPlanesLeft = 10;
      playerOnePlanesLeft = 10;
      BuildBoxes();
    }

    int WhichPlane(Plane plane)
    {
      if (GetPlane(0) == plane)
        return 0;
      else
        return 1;
    }

    private CollisionRule PlaneShotRule()
    {
      return new CollisionRule(typeof(Plane), typeof(Shot), delegate(object o1, object o2)
      {
        db.Remove(o2);
        int plane = WhichPlane((Plane)o1);
        KillPlane(plane);
      });
    }

    private void KillPlane(int plane)
    {
      DecPlayerLives(plane);
      ResetPlane(plane);
    }

    private CollisionRule PlaneBottomBoxRule()
    {
      return new CollisionRule(typeof(Plane), typeof(BottomBox), delegate(object o1, object o2)
      {
        for (int i = 0; i < 2; i++)
          if (Object.ReferenceEquals(o1, GetPlane(i)))
            KillPlane(i);
      });
    }

    private int GetInitialPlaneDirection(int i)
    {
      return i == 0 ? 0 : 180;
    }

    private int GetInitialPlaneXPos(int i)
    {
      return i == 0 ? -50 : 50;
    }

    CollisionRule PlaneTopBoxRule()
    {
      return new CollisionRule(typeof(Plane), typeof(TopBox), delegate(object o1, object o2)
      {
        ((Plane)o1).StartFalling();
      });
    }

    private CollisionRule PlaneSideBoxRule()
    {
      return new CollisionRule(typeof(Plane), typeof(SideBox), delegate(object o1, object o2)
      {
        Plane plane = o1 as Plane;
        SideBox sidebox = o2 as SideBox;
        sidebox.ApplyToPlane(plane);
      });
    }

    private class CollisionBox { 
    }

    private class TopBox : CollisionBox, ICollideable
    {
      readonly Box2d box;

      public TopBox(Box2d box)
      {
        this.box = box;
      }

      public Box2d GetBox2d()
      {
        return box;
      }
    }

    private class BottomBox : CollisionBox, ICollideable
    {
      Box2d box;

      public BottomBox(Box2d box)
      {
        this.box = box;
      }

      public Box2d GetBox2d()
      {
        return box;
      }
    }

    private class SideBox : CollisionBox, ICollideable
    {
      readonly Box2d box;
      readonly double planeOffset;

      public SideBox(Box2d box, double planeOffset)
      {
        this.box = box;
        this.planeOffset = planeOffset;
      }

      public Box2d GetBox2d()
      {
        return box;
      }

      public void ApplyToPlane(Plane plane)
      {
        plane.X += planeOffset;
      }
    }

    int planePlaneColls = 0;
    private CollisionRule PlanePlaneRule()
    {
      return new CollisionRule(typeof(Plane), typeof(Plane), delegate(object o1, object o2)
            {
              planePlaneColls++;
              if (planePlaneColls % 2 == 0)
              {
                DecPlayerLives(0);
                DecPlayerLives(1);
              }
              ResetPlanes();
            });
    }

    private CollisionRule ShotCollisionBoxRule()
    {
      return new CollisionRule(typeof(Shot), typeof(CollisionBox), delegate(object o1, object o2)
            { db.Remove(o1); });
    }

    private void ResetPlanes()
    {
      for (int i = 0; i < 2; i++)
        ResetPlane(i);
    }

    Plane GetPlane(int i) { return planes[i]; }

    Mesh GetPlaneMesh(int plane)
    {
      return plane == 0 ? Mesh.RedPlane : Mesh.YellowPlane;
    }

    private void ResetPlane(int plane)
    {
      if (GetPlane(plane) != null)
        db.Remove(GetPlane(plane));
      SetPlane(plane, new Plane(Calc.v(GetInitialPlaneXPos(plane), 10), Calc.DegreesToRadians(GetInitialPlaneDirection(plane)), 10));
      GetPlane(plane).Mesh = GetPlaneMesh(plane);
      db.Add(GetPlane(plane));
    }

    private void SetPlane(int plane, Plane planeInstance)
    {
      planes[plane] = planeInstance;
    }

    double gamewidth = 60;

    public Game()
    {
      ResetGame();
    }

    private void BuildBoxes()
    {
      double planeOffset = gamewidth * 1.9;
      db.Add(new SideBox(LeftBoxGeom(), planeOffset));
      db.Add(new SideBox(RightBoxGeom(), -planeOffset));
      db.Add(new TopBox(TopBoxGeom()));
      db.Add(new BottomBox(BottomBoxGeom()));
    }

    public void DecPlayerLives(int plane)
    {
      if (plane == 0)
        playerOnePlanesLeft--;
      else
        playerTwoPlanesLeft--;
      SystemSounds.Asterisk.Play();
    }

    public void Tick(double deltaMS)
    {
      UpdateActiveObjects(deltaMS);
      HandleCollisions();
    }

    private void UpdateActiveObjects(double deltaMS)
    {
      foreach (IActive active in db.Select(typeof(IActive)))
        active.Tick(deltaMS);
    }

    private void HandleCollisions()
    {
      List<Collision<object>> colls = FindCollisions();
      ApplyCollisionRules(colls);
    }

    private void ApplyCollisionRules(List<Collision<object>> colls)
    {
      foreach (Collision<object> coll in colls)
        foreach (CollisionRule rule in rules)
        {
          rule.ApplyTo(coll.One, coll.Two);
          rule.ApplyTo(coll.Two, coll.One);
        }
    }

    private List<Collision<object>> FindCollisions()
    {
      Box2dCollisionDetector<object> d = new Box2dCollisionDetector<object>();
      foreach (ICollideable coll in db.Select(typeof(ICollideable)))
        d.AddBox(coll.GetBox2d(), coll);
      List<Collision<object>> colls = d.Detect();
      return colls;
    }

    private Box2d RightBoxGeom()
    {
      return new Box2d(gamewidth, 0, -1, 1, -20, 50, 0);
    }

    private Box2d TopBoxGeom()
    {
      return new Box2d(0, 50, -gamewidth, gamewidth, -1, 1, 0);
    }

    private Box2d LeftBoxGeom()
    {
      return new Box2d(-gamewidth, 0, -1, 1, -20, 50, 0);
    }

    private Box2d BottomBoxGeom()
    {
      return new Box2d(0, -20, -gamewidth, gamewidth, -1, 1, 0);
    }

    public int PlayerOnePlanes
    {
      get { return playerOnePlanesLeft; }
    }

    public int PlayerTwoPlanes
    {
      get { return playerTwoPlanesLeft; }
    }

    public void Render(IRenderer renderer)
    {
      if (renderCollBoxes != 0)
        RenderCollisionBoxes(renderer);
      foreach (IRenderable renderable in db.Select(typeof(IRenderable)))
        renderable.Render(renderer);
    }

    private class Backdrop : IRenderable
    {
      public void Render(IRenderer renderer)
      {
        renderer.PushMatrix();
        renderer.Translate(new Vector3(0, -200, -50));
        renderer.DrawMesh(Mesh.Backdrop);
        renderer.PopMatrix();
      }
    }

    private void RenderCollisionBoxes(IRenderer renderer)
    {
      foreach (ICollideable coll in db.Select(typeof(ICollideable)))
        RenderBox(renderer, coll.GetBox2d());
    }

    Vector3 Logic2World(double x, double y)
    {
      return new Vector3((float)x, 0, (float)y);
    }

    void RenderBox(IRenderer renderer, Box2d box)
    {
      renderer.PushMatrix();

      renderer.Translate(Logic2World(box.X, box.Y));
      renderer.Rotate(-Vector3.UnitY, box.Rotation);

      float amin = (float)box.Amin;
      float amax = (float)box.Amax;
      float bmin = (float)box.Bmin;
      float bmax = (float)box.Bmax;

      renderer.DrawLine(new Vector3(amin, 0, bmin), new Vector3(amin, 0, bmax));
      renderer.DrawLine(new Vector3(amax, 0, bmin), new Vector3(amax, 0, bmax));
      renderer.DrawLine(new Vector3(amin, 0, bmin), new Vector3(amax, 0, bmin));
      renderer.DrawLine(new Vector3(amin, 0, bmax), new Vector3(amax, 0, bmax));

      renderer.PopMatrix();
    }

    public void PlayerOneFirePushed()
    {
      db.Add(GetPlane(0).Fire());
    }

    public void PlayerTwoFirePushed()
    {
      db.Add(GetPlane(1).Fire());
    }

    public void PlayerOneLiftPushed()
    {
      GetPlane(0).LiftPushed();
    }

    public void PlayerOneDivePushed()
    {
      GetPlane(0).DivePushed();
    }

    public void PlayerOneDiveReleased()
    {
      GetPlane(0).DiveReleased();
    }

    public void PlayerOneLiftReleased()
    {
      GetPlane(0).LiftReleased();
    }

    public void PlayerTwoLiftPushed()
    {
      GetPlane(1).DivePushed();
    }

    public void PlayerTwoDivePushed()
    {
      GetPlane(1).LiftPushed();
    }

    public void PlayerTwoDiveReleased()
    {
      GetPlane(1).LiftReleased();
    }

    public void PlayerTwoLiftReleased()
    {
      GetPlane(1).DiveReleased();
    }

    double renderCollBoxes = 1;
    internal void TweakConfig(TweakConfig config)
    {
      gamewidth = config.GetPropertyValue("gamewidth", gamewidth);
      BuildBoxes();
      renderCollBoxes = config.GetPropertyValue("renderCollisionBoxes", renderCollBoxes);
      Plane.TweakConfig(config);
    }

    public void PlanePadChange(PlaneName name, PlanePad pad, PlanePadState state)
    {
      Plane plane = GetPlane((int)name);
      switch (pad)
      {
        case PlanePad.Dive:
          if (state == PlanePadState.Pressed)
            plane.DivePushed();
          else
            plane.DiveReleased();
          break;
        case PlanePad.Lift:
          if (state == PlanePadState.Pressed)
            plane.LiftPushed();
          else
            plane.LiftReleased();
          break;
        case PlanePad.Fire:
          db.Add(plane.Fire());
          break;
      }
    }
  }
}
